package jp.ac.aiit.spg.service.impl;

import java.util.*;

import javax.ejb.*;

import jp.ac.aiit.spg.service.SpgService;
import jp.ac.aiit.spg.web.wbs.SpgTaskTemplete;
import jp.ac.aiit.spg.entity.*;
import jp.ac.aiit.spg.common.*;
import jp.ac.aiit.spg.dao.*;

@Stateless
public class SpgServiceImpl implements SpgService {

	@EJB
	private SpgUserDao spgUserDao;

	@EJB
	private SpgTaskDao spgTaskDao;

	@EJB
	private SpgProjectDao spgProjectDao;

	@EJB
	private SpgMemberDao spgMemberDao;

	@EJB
	private SpgWBSDao spgWBSDao;

	@EJB
	private SpgToDoDao spgToDoDao;

	// プロジェクトの操作 ******************************************************
	// *************************************************************************

	/**
	 * システム上のすべてのプロジェクトのリストを取得する
	 * 
	 * @parm なし
	 */
	public List<SpgProject> listAllProject() {
		List<SpgProject> allproject = spgProjectDao.findAll("name");
		return allproject;
	}

	/**
	 * 一人のユーザーが所属するすべてのプロジェクトのリストを取得する
	 * 
	 * @parm int ユーザーID
	 */
	public List<SpgProject> listUserProject(int userId) {

		Map<String, SpgProject> pMap = new TreeMap<String, SpgProject>();

		SpgUser u = spgUserDao.find(userId);
		Set<SpgMember> mSet = u.getMembers();

		for (SpgMember m : mSet) {
			pMap.put(m.getProject().getName(), m.getProject());
		}

		List<SpgProject> pList = new ArrayList<SpgProject>();

		Collection<SpgProject> pCol = pMap.values();
		for (SpgProject p : pCol) {
			pList.add(p);
		}

		return pList;
	}

	/**
	 * 一人のユーザーが所属するすべてのプロジェクトのリストを取得する （但し、指定したロールがあるもののみ）
	 * 
	 * @parm int ユーザーID
	 * @parm SpgRole ロール
	 */
	public List<SpgProject> listUserProject(int userId, SpgRole role) {

		// ********** TODO **************

		Map<String, SpgProject> pMap = new TreeMap<String, SpgProject>();

		SpgUser u = spgUserDao.find(userId);
		Set<SpgMember> mSet = u.getMembers();

		for (SpgMember m : mSet) {
			if (m.getRole() == role) {
				pMap.put(m.getProject().getName(), m.getProject());
			}
		}

		List<SpgProject> pList = new ArrayList<SpgProject>();

		Collection<SpgProject> pCol = pMap.values();
		for (SpgProject p : pCol) {
			pList.add(p);
		}

		return pList;
	}

	/**
	 * プロジェクトを作成する
	 * 
	 * @parm String プロジェクト名
	 * @parm String プロジェクトの説明
	 */
	public void createProject(String name, String description) {

		// プロジェクト・エンティティを作成して永続化する。
		SpgProject p = new SpgProject();
		p.setName(name);
		p.setDescription(description);
		p.setStartDate(new Date());
		p.setEndDate(new Date());
		spgProjectDao.persist(p);

		// プロジェクトのWBSエンティティを作成して永続化する。
		SpgWBS w = new SpgWBS();
		p.setWbs(w);
		w.setProject(p);
		spgWBSDao.persist(w);

		// プロジェクトのルート・タスクを作成して永続化する。
		SpgTask t = new SpgTask();
		t.setName("*ROOT");
		t.setWbsNumber("0");
		spgTaskDao.persist(t);
		t.setWbs(w);

	}

	/**
	 * １つのプロジェクトを名前で検索する
	 * 
	 * @parm String プロジェクト名
	 */
	public SpgProject findProject(String projectName) {
		SpgProject sp = spgProjectDao.findByName(projectName);
		return sp;
	}

	/**
	 * １つのプロジェクトを取得する
	 * 
	 * @parm int プロジェクトID
	 */
	public SpgProject getProject(int projectId) {
		SpgProject sp = spgProjectDao.find(projectId);
		return sp;
	}

	// ユーザーの操作 ******************************************************
	// *********************************************************************

	/**
	 * システム上のすべてのユーザーのリストを取得する
	 * 
	 * @parm なし
	 */
	public List<SpgUser> listAllUser() {
		List<SpgUser> alluser = spgUserDao.findAll();
		return alluser;
	}

	/**
	 * ユーザーを作成する
	 * 
	 * @parm String ユーザー名
	 * @parm String ユーザーの説明
	 * @parm String パスワード
	 */
	public void createUser(String name, String description, String password) {
		// ユーザー・エンティティを作成して永続化する。
		SpgUser u = new SpgUser();
		u.setName(name);
		u.setDescription(description);
		u.setPassword(password);
		spgUserDao.persist(u);

		// ユーザーのToDoエンティティを作成して永続化する。
		SpgToDo td = new SpgToDo();
		u.setTodo(td);
		td.setUser(u);
		spgToDoDao.persist(td);

		// ユーザーの個人プロジェクトを作成する。
		this.createProject(name, "個人タスク: " + description);
		SpgProject p = this.findProject(name);
		this.addMember(p.getId(), u.getId(), SpgRole.PROJECTMANAGER);

	}

	/**
	 * ユーザー名でユーザーのエンティティを検索、取得する。 見つからない場合にはnullを返す。
	 * 
	 * @parm String ユーザー名
	 */
	public SpgUser findUser(String userName) {

		SpgUser u = null;

		try {
			u = spgUserDao.findByName(userName);
		} catch (javax.persistence.NoResultException e) {
			u = null;
		}

		return u;
	}

	/**
	 * １人のユーザーを取得する
	 * 
	 * @parm int ユーザーID
	 */
	public SpgUser getUser(int userId) {
		SpgUser su = spgUserDao.find(userId);
		return su;
	}

	/**
	 * ユーザー名とパスワードの正しさを確認する
	 * 
	 * @parm String ユーザー名
	 * @parm String パスワード
	 */
	public boolean isValidUser(String name, String password) {

		SpgUser u = null;

		try {
			u = spgUserDao.findByName(name);
		} catch (javax.persistence.NoResultException e) {
			return false;
		}

		if (password.equals(u.getPassword())) {
			return true;
		} else {
			return false;
		}

	}

	// タスクの操作 *************************************************************
	// **************************************************************************

	/**
	 * １つのプロジェクトの全てのタスクのリストを取得する
	 * 
	 * @parm int プロジェクトID
	 * @parm String[] ソートの指定（タスク属性のリスト）
	 */
	public List<SpgTask> listAllProjectTask(int projectId, String[] sortOrder) {

		SpgProject sp = spgProjectDao.find(projectId);
		SpgWBS sw = sp.getWbs();
		List<SpgTask> result = spgTaskDao.findByProject(sw.getId(), sortOrder);

		// ダミーのルートタスク（常にWBS番号ゼロ）を除去
		result.remove(0);

		return result;

	}

	/**
	 * １つのプロジェクト内で指定した承認状態の全てのタスクのリストを取得する
	 * 
	 * @parm int プロジェクトID
	 * @parm SpgAssignStatus 承認状態
	 */
	public List<SpgTask> listProjectTasksByAssignStatus(int projectid,
			SpgAssignStatus assignstatus) {

		SpgProject sp = spgProjectDao.find(projectid);
		SpgWBS sw = sp.getWbs();

		List<SpgTask> result = spgTaskDao.findByProjectAndAssignStatus(sw
				.getId(), assignstatus, "wbsNumber");

		return result;
	}

	/**
	 * １人のユーザーの指定した承認状態の全てのタスクのリストを取得する
	 * 
	 * @parm int ユーザーID
	 * @parm SpgAssignStatus 承認状態
	 * @parm String[] ソートの指定（タスク属性のリスト）
	 */
	public List<SpgTask> listUserTasksByAssignStatus(int userId,
			SpgAssignStatus assignStatus, String[] sortOrder) {

		SpgUser su = spgUserDao.find(userId);
		SpgToDo st = su.getTodo();

		List<SpgTask> result = spgTaskDao.findByUserTasksAndAssignStatus(st
				.getId(), assignStatus, sortOrder);

		return result;
	}

	/**
	 * １つのプロジェクト内で指定したメンバーの全てのタスクのリストを取得する
	 * 
	 * @parm int プロジェクトID
	 * @parm SpgMember メンバー
	 */
	public List<SpgTask> listProjectTasksByMember(int projectid, int memberid) {

		SpgProject sp = spgProjectDao.find(projectid);
		SpgWBS sw = sp.getWbs();
		SpgMember member = spgMemberDao.find(memberid);

		List<SpgTask> result = spgTaskDao.findByProjectAndMember(sw.getId(),
				member, "wbsNumber");

		return result;

	}

	/**
	 * １つのプロジェクト内で指定したメンバーの指定した承認状態の全てのタスクのリストを取得する
	 * 
	 * @parm int プロジェクトID
	 * @parm SpgAssignStatus 承認状態
	 */
	public List<SpgTask> listProjectTasksByMemberAndAssignStatus(int projectid,
			int memberid, SpgAssignStatus assignstatus) {

		SpgProject sp = spgProjectDao.find(projectid);
		SpgWBS sw = sp.getWbs();
		SpgMember member = spgMemberDao.find(memberid);

		List<SpgTask> result = spgTaskDao
				.findByProjectAndMemberAndAssignedStatus(sw.getId(), member,
						assignstatus, "wbsNumber");

		return result;

	}

	/**
	 * プロジェクト内のタスクをWBS番号で検索する
	 * 
	 * @parm int プロジェクトID
	 * @param String
	 *            WBS番号
	 */
	public SpgTask findTask(int projectId, String wbsNumber) {

		SpgProject sp = spgProjectDao.find(projectId);
		SpgWBS sw = sp.getWbs();

		SpgTask st = spgTaskDao.getByProjectAndWbsNumber(sw.getId(), wbsNumber);

		return st;

	}

	/**
	 * １つのタスクを取得する
	 * 
	 * @parm int タスクID
	 */
	public SpgTask getTask(int taskId) {
		SpgTask st = spgTaskDao.find(taskId);
		return st;
	}

	/**
	 * プロジェクト内に１つのタスクを作成する（改善バージョン）
	 * 
	 * @parm SpgTaskTemplete タスク作成テンプレート
	 */
	public void createTask(SpgTaskTemplete stt) {

		// タスクを作成する
		SpgTask st = new SpgTask();

		// タスクの属性の設定
		st.setName(stt.name);

		st.setWorkingTime(stt.workingTime);
		if (stt.isStartTimeSpecifyedByUser == true) {
			st.setStartTimeSpecifiedByUser(true);
		}

		st.setStartTimePlanned(stt.startTimePlanned);

		st.setAssignment(stt.assignment);

		st.setProgress(SpgProgressStatus.UNTOUCHED);

		st.setDeliverableMemo(stt.deliverableMemo);
		st.setAssignMemo("");

		// タスクの関連の設定

		// WBSとの関連を登録する
		SpgProject p = spgProjectDao.find(stt.projectId);
		st.setWbs(p.getWbs());

		// メンバーとの関連を登録する

		// 承認状態がCREATEDの場合には、メンバーは必ず*NOBODYにする
		if (stt.assignment == SpgAssignStatus.CREATED) {

			Set<SpgMember> ml = p.getMembers();
			for (SpgMember m2 : ml) {
				String userName = m2.getUser().getName();
				if (userName.equals("*NOBODY")) { // 美しくないが納期優先
					stt.memberId = m2.getId();
					break;
				}
			}
		}

		SpgMember m = spgMemberDao.find(stt.memberId);
		st.setMember(m);

		// ToDoとの関連を登録する
		st.setTodo(m.getUser().getTodo());

		// タスク間の関連の設定

		// タスクの階層の設定
		SpgTask parent = spgTaskDao.find(stt.parentTaskId);
		st.setParent(parent);

		// タスクの順序の設定
		// タスク・テンプレートの先行タスクリストが空でない場合
		if (stt.PredecessorIds.size() != 0) {

			// 画面上で”先行タスク無し”が選ばれていない場合
			if (stt.PredecessorIds.get(0) != SpgConstant.NOT_A_TASK) {

				for (int taskId : stt.PredecessorIds) {
					SpgTask predecessor = spgTaskDao.find(taskId);
					st.addPredecessor(predecessor);
				}
			}
		}

		st.calcWbsNumber(); // WBS番号を再帰的に生成
		st.reCalcWorkingTime(); // 作業時間を再帰的に計算

		// 完了予定日時を計算
		st.calcFinishTimePlanned();	  // まず自分の完了予定日時を計算
		st.reCalcFinishTimePlanned(); // 次に、上位タスクの完了予定日時を再帰的に計算

		
		// 永続化
		spgTaskDao.persist(st); // ここでテーブルに行は挿入される

	}

	/**
	 * タスクを更新する（改善バージョン）
	 * 
	 * @parm タスクID
	 * @parm SpgTaskTemplete タスク作成テンプレート
	 */
	public void updateTask(int taskId, SpgTaskTemplete stt) {
		// TODO 自動生成されたメソッド・スタブ

		// タスクを検索する
		SpgTask st = spgTaskDao.find(taskId);

		// タスクの属性の設定
		st.setName(stt.name);

		st.setWorkingTime(stt.workingTime);

		if (stt.isStartTimeSpecifyedByUser == true) {
			st.setStartTimeSpecifiedByUser(true);
		}

		st.setStartTimePlanned(stt.startTimePlanned);
		st.setDeliverableMemo(stt.deliverableMemo);
		st.setAssignMemo(stt.assignMemo);

		// タスクの関連の設定

		// メンバーとの関連を登録する
		SpgProject p = spgProjectDao.find(stt.projectId);

		// 承認状態がCREATEDの場合には、メンバーは必ず*NOBODYにする
		if (stt.assignment == SpgAssignStatus.CREATED) {

			Set<SpgMember> ml = p.getMembers();
			for (SpgMember m2 : ml) {
				String userName = m2.getUser().getName();
				if (userName.equals("*NOBODY")) { // 美しくないが納期優先
					stt.memberId = m2.getId();
					break;
				}
			}
			// 承認状態がASSIGNEDの場合には、そう設定する
		} else if (stt.assignment == SpgAssignStatus.ASSIGNED) {
			st.setAssignment(stt.assignment);
		} else {
			// それ以外の場合、承認状態は何も変更しない
			;
		}

		// メンバーが変わっている場合には、メンバーおよびToDoとの関連をつけ直す。
		if (stt.memberId != st.getMember().getId()) {

			// 以前のメンバーとの関連を解除する
			// 以前のメンバーからそのタスクを除去
			SpgMember oldm = spgMemberDao.find(st.getMember().getId());
			oldm.removeTask(st);
			// 以前のユーザーのToDoからタスクを除去
			SpgToDo oldtd = spgToDoDao.find(st.getTodo().getId());
			oldtd.removeTask(st);

			// 新しいメンバーとの関連を登録する
			SpgMember m = spgMemberDao.find(stt.memberId);
			st.setMember(m);
			st.setTodo(m.getUser().getTodo());

			// 承認・拒否コメントをクリアする
			st.setAssignMemo("");
		}

		// タスク間の関連の設定

		// 親タスクが変わっている場合
		if (stt.parentTaskId != st.getParent().getId()) {

			// 自分のWBS番号を再生成
			SpgTask parent = spgTaskDao.find(stt.parentTaskId);
			st.setParent(parent);
			st.calcWbsNumber();

			// 自分の子のWBS番号を再生成
			// （WBS番号の全面ふり直しの部分機能に相当）
			st.reCalcWbsNumber();

		}

		// 先行タスクが変わっている場合
		// いったんすべての先行タスクを削除
		Set<SpgTask> oldPredecessorTasks = st.getPredecessors();
		if (oldPredecessorTasks.size() != 0) {
			/*
			for (SpgTask pred : oldPredecessorTasks) {
				int a = 1;
				st.removePredecessor(pred);
				a = 2;
			}
			*/
			
			// 緊急フィックス 2/22
			st.getPredecessors().clear();

		}

		// 先行タスクありか、なしか？あれば設定。
		// タスク・テンプレートの先行タスクリストが空の場合
		if (stt.PredecessorIds.size() != 0) {

			// 画面上で”先行タスク無し”が選ばれていた場合
			if (stt.PredecessorIds.get(0) != SpgConstant.NOT_A_TASK) {

				for (int predecessorTaskId : stt.PredecessorIds) {
					SpgTask predecessor = spgTaskDao.find(predecessorTaskId);
					st.addPredecessor(predecessor);
				}

			}
		}

		
		// 作業時間を再帰的に計算
		// （上位タスクが変更されたかもしれないので無条件で行うこととする）
		st.reCalcWorkingTime(); 
	
		// 完了予定日時を計算
		st.calcFinishTimePlanned();	  // まず自分の完了予定日時を計算
		st.reCalcFinishTimePlanned(); // 次に、上位タスクの完了予定日時を再帰的に計算

		
		// 永続化
		spgTaskDao.merge(st); // ここでエンティティを更新する

	}

	/**
	 * １つのタスクを削除する
	 * 
	 * @parm int タスクID
	 */
	public void deleteTask(int taskId) {

		SpgTask st = spgTaskDao.find(taskId);

		// もし子タスクがあれば、まず最初にすべての子タスクを除去する必要がある。
		if (st.getChildren().size() != 0) {

			List<SpgTask> children = st.getChildren();
			for (SpgTask t : children) {
				this.deleteTask(t.getId());
			}

		}


		
		// 関連の除去
		st.getMember().removeTask(st); // まず以前のメンバーからそのタスクを除去
		st.getWbs().removeTask(st); // WBSからタスクを除去
		st.getTodo().removeTask(st); // ToDoからタスクを除去

		// 自分が他のタスクの先行タスクになっている場合、その関連も除去（外部キー制約違反になるため）
		if (st.getSuccessors().size() != 0) {
			Set<SpgTask> successors = st.getSuccessors();
			for (SpgTask t : successors) {
				t.removePredecessor(st);
			}

		}

		// 最後に上位タスクの作業時間を再計算
		st.setWorkingTime(0);
		st.reCalcWorkingTime(); // 作業時間を再帰的に計算


		// タスクを削除
		spgTaskDao.remove(st);

		
	}

	/**
	 * タスクを割り当てる
	 * 
	 * @parm int タスクID
	 * @parm int メンバーID
	 */
	public void assignTask(int taskId, int memberId) {

		SpgTask st = spgTaskDao.find(taskId);

		SpgMember m = spgMemberDao.find(memberId);

		// もしメンバーが変更されていたらエンティティ間の関連も更新する。
		if (memberId != st.getMember().getId()) {

			// まず
			// 以前のメンバーからそのタスクを除去
			SpgMember oldm = spgMemberDao.find(st.getMember().getId());
			oldm.removeTask(st);
			// 以前のユーザーのToDoからタスクを除去
			SpgToDo oldtd = spgToDoDao.find(st.getTodo().getId());
			oldtd.removeTask(st);
		}

		st.setMember(m); // タスクに指定されたメンバーを設定
		st.setTodo(m.getUser().getTodo()); // タスクにToDoを追加

	}

	/**
	 * 割り当てられたタスクを承認する
	 * 
	 * @parm int タスクID
	 */
	public void acceptTask(int taskId) {

		SpgTask st = spgTaskDao.find(taskId);

		// 依頼中から承認へ
		if (st.getAssignment() == SpgAssignStatus.ASSIGNED) {
			st.setAssignment(SpgAssignStatus.ACCEPTED);
			st.setAssignMemo("");
		}

	}

	/**
	 * 割り当てられたタスクを拒否する
	 * 
	 * @parm int タスクID
	 * @parm SpgProgressStatus 進捗状況
	 */
	public void rejectTask(int taskId, String assignMemo) {

		SpgTask st = spgTaskDao.find(taskId);

		// 依頼中から拒否へ
		if (st.getAssignment() == SpgAssignStatus.ASSIGNED) {
			st.setAssignment(SpgAssignStatus.REJECTED);
			st.setAssignMemo(assignMemo);
		}

	}

	/**
	 * タスクの進捗状況を変更する
	 * 
	 * @parm int タスクID
	 * @parm SpgProgressStatus 進捗状況
	 */
	public void updateTaskProgress(int taskId, SpgProgressStatus progressStatus) {

		SpgTask st = spgTaskDao.find(taskId);

		// 未着手から着手へ
		if (st.getProgress() == SpgProgressStatus.UNTOUCHED) {
			if (progressStatus == SpgProgressStatus.STARTED) {
				st.setProgress(progressStatus);
				st.setStartTimeActual(new Date());
			}
		}

		// 着手から完了へ
		if (st.getProgress() == SpgProgressStatus.STARTED) {
			if (progressStatus == SpgProgressStatus.FINISHED) {
				st.setProgress(progressStatus);
				st.setFinishTimeActual(new Date());
			}
		}

	}

	/**
	 * プロジェクトのルートタスクを取得する
	 * 
	 * @parm int プロジェクトID
	 */
	public SpgTask getRootTask(int projectId) {

		SpgProject sp = spgProjectDao.find(projectId);
		SpgWBS sw = sp.getWbs();

		SpgTask st = spgTaskDao.getByProjectAndWbsNumber(sw.getId(), "0");

		return st;
	}

	// メンバーの操作 ******************************************************
	// *********************************************************************

	/**
	 * １つのプロジェクトの全てのメンバーのリストを取得する
	 * 
	 * @parm int プロジェクトID
	 */
	public List<SpgMember> listAllMember(int projectId) {

		SpgProject p = spgProjectDao.find(projectId);
		Set<SpgMember> allMemberSet = p.getMembers();

		// ソート
		Map<String, SpgMember> sorted = new TreeMap<String, SpgMember>();
		for (SpgMember m : allMemberSet) {
			sorted.put(m.getUser().getName(), m);
		}

		// 型変換
		Collection<SpgMember> allMemberCol = sorted.values();
		List<SpgMember> allMemberList = new ArrayList<SpgMember>();
		for (SpgMember m : allMemberCol) {
			allMemberList.add(m);
		}

		return allMemberList;

	}

	/**
	 * プロジェクトのメンバーとしてユーザーを追加する
	 * 
	 * @parm int プロジェクトID
	 * @parm int ユーザーID
	 * @parm SpgRole 役割
	 */
	public SpgMember addMember(int projectId, int userId, SpgRole role) {

		// メンバー・エンティティを作成して関連を設定し、永続化する。
		SpgMember m = new SpgMember();

		// 見つからない場合のエラーチェックしていない。
		SpgProject p = spgProjectDao.find(projectId);
		SpgUser u = spgUserDao.find(userId);

		// 重複の場合のエラーチェックしていない。
		p.addMember(m);
		u.addMember(m);
		m.setRole(role);

		spgMemberDao.persist(m);

		return m;

	}

	/**
	 * ToDoの取得(ToDoの初期表示時使用）
	 */
	public SpgToDo getToDoByUserId(int userId, SpgAssignStatus assignStatus,
			String[] sortOrder) {

		return null;
	}

	/**
	 * ToDoの取得
	 */
	public SpgToDo getToDoByUserId(int userId) {
		
		/* original code (by shino) ******
		SpgToDo todo = this.getUser(userId).getTodo();
		return todo;
		****** original code */
		
		
		/* test code */
		SpgUser u = spgUserDao.find(userId);
		SpgToDo todo = spgToDoDao.find(u.getTodo().getId());
		return todo;
		
		
		
	}

	/**
	 * ToDoのタスクを更新
	 */
	public void updateToDo(List<SpgTask> tasks) {
		for(SpgTask t:tasks){
			spgTaskDao.persist(t);
		}	
	}
	
	/**
	 * WBS番号の全面的なふり直し
	 */
	public void reCalcWbsNumber(int projectId) {

		SpgTask root = this.getRootTask(projectId);
		root.reCalcWbsNumber();
		
	}

	public void updateTask(SpgTask task){
		
		spgTaskDao.merge(task);
	}
	
	
}
